﻿using System;
using System.IO;
using System.Reflection;
using System.Collections.Generic;
using AudioPlayer.Plugin.Tags;
using AudioPlayer.Plugin.Playback;
using AudioPlayer.Plugin.Storage;
using System.Collections;
using System.Xml;

namespace AudioPlayer.Plugin {
	internal class PluginLoader {
		#region Variables
		private PluginHash _plugins;

		private PluginBase<ITagProvider> _tagActive;
		private PluginBase<IPlayback> _playbackAudio;
		private PluginBase<IWatcher> _watcher;
		private PluginBase<IStorage> _storage;
		private PluginBase<IPlayControl> _playControl;
		private PluginBase<ISettings> _settings;
		#endregion
		#region Constructor
		public PluginLoader() {
			//Create full collection
			_plugins = new PluginHash();
		}
		#endregion
		#region Private Voids
		/// <summary>
		/// Reads the XML config document.
		/// </summary>
		/// <param name="doc">The document.</param>
		private void ReadXmlDocument(XmlDocument doc) {
			//Document must have atleast two child nodes
			//First xml tag
			//Second plugins tag
			if (doc.ChildNodes.Count > 1) {
				//Loop through
				foreach (XmlNode node in doc.ChildNodes[1].ChildNodes) {
					//Make sure it's ok
					try {
						if (node != null) {
							//Read everything to strings
							string plgName = (node.Attributes["Name"] != null ? node.Attributes["Name"].Value : null);
							string plgGuid = (node.Attributes["Guid"] != null ? node.Attributes["Guid"].Value : null);
							string plgAuto = (node.Attributes["AutoLoad"] != null ? node.Attributes["AutoLoad"].Value : null);
							string plgPriority = (node.Attributes["Priority"] != null ? node.Attributes["Priority"].Value : null);
							string plgLastSeen = (node.Attributes["LastSeen"] != null ? node.Attributes["LastSeen"].Value : null);
							string plgLoaded = (node.Attributes["Loaded"] != null ? node.Attributes["Loaded"].Value : null);
							string plgFail = (node.Attributes["Fail"] != null ? node.Attributes["Fail"].Value : null);
							//Check if guid is ok
							Guid key = new Guid(plgGuid);
							if (key != null && _plugins.Contains(key)) {
								PluginBase plugin = _plugins[key];
								if (plugin != null) {
									//Convert stuff
									if (!String.IsNullOrEmpty(plgAuto) && plgAuto.ToLower() == "true") {
										plugin.AutoLoad = true;
									} else {
										plugin.AutoLoad = false;
									}
									int fail = 0;
									int loaded = 0;
									int.TryParse(plgLoaded, out loaded);
									int.TryParse(plgFail, out fail);

									plugin.TotalFail = fail;
									plugin.TotalLoad = loaded;
								}
							}
						}
					} catch { }
				}
			}
		}

		private void Host(PluginBase plugin) {
			//Check if everyting is ok
			if (plugin != null && plugin.Plugin != null) {
				if (plugin.Plugin is IPluginHost) {
					IPluginHost hostedPlugin = (IPluginHost)plugin.Plugin;
					hostedPlugin.Host = new PluginHost(plugin);
				}
			}
		}

		/// <summary>
		/// Creates the domain.
		/// </summary>
		/// <param name="filename">The filename of plugin.</param>
		/// <returns>AppDomain or Null</returns>
		private AppDomain CreateDomain(String filename) {
			if (File.Exists(filename)) {
				try {
					AppDomainSetup appSetup = new AppDomainSetup();
					appSetup.ApplicationBase = Environment.CurrentDirectory + "\\Plugin\\";

					AppDomain domain = AppDomain.CreateDomain(filename, null, appSetup);

					return domain;
				} catch {
					return null;
				}
			}
			return null;
		}

		/// <summary>
		/// Creates the assembly.
		/// </summary>
		/// <param name="domain">The domain.</param>
		/// <param name="filename">The filename.</param>
		/// <returns>Assembly or NULL</returns>
		private Assembly CreateAssembly(AppDomain domain, String filename) {
			if (File.Exists(filename)) {
				try {
					AssemblyName asmName = AssemblyName.GetAssemblyName(filename);
					Assembly asm = domain.Load(asmName);
					return asm;
				} catch {
					return null;
				}
			}
			return null;
		}
		#endregion
		#region Protected Voids
		protected void OnPluginFound(PluginBase plugin) {
			if (plugin != null && this.PluginFound != null) {
				this.PluginFound(plugin);
			}
		}
		#endregion
		#region Public Voids
		/// <summary>
		/// Searches the specified path.
		/// </summary>
		/// <param name="searchPath">The Path.</param>
		public void Search(string searchPath) {
			DirectoryInfo dirInfo = new DirectoryInfo(searchPath);

			if (dirInfo.Exists) {
				FileInfo[] fInfo = dirInfo.GetFiles("*.dll");
				foreach (FileInfo file in fInfo) {
					Load(file);
				}
			}
		}
		/// <summary>
		/// Loads the specified file, and searches for availible plugins.
		/// </summary>
		/// <param name="file">The file.</param>
        public void Load(FileInfo file) {
			AppDomain domain = null;
			bool plgFound = false;
			try {
				//If we are still lucky, create domain for plugins
				domain = this.CreateDomain(file.FullName);
				Assembly asm = this.CreateAssembly(domain, file.FullName);

				if (domain != null) {
					//loop through all types
					foreach (Type type in asm.GetTypes()) {
						if (type.IsClass) {
							Type t = type.GetInterface(typeof(IPlugin).FullName);
							if (t != null) {
								//Create base plugin class
								//And add to collection
								PluginBase basePlugin = new PluginBase(type);
								basePlugin.Domain = domain;
								basePlugin.Assembly = asm;

								plgFound = true;
								_plugins.Add(basePlugin);
							}
						}
					}
				}

			} catch {
				if (domain != null) {
					AppDomain.Unload(domain);
					domain = null;
				}
			} finally {
				if (!plgFound && domain != null) {
					AppDomain.Unload(domain);
					domain = null;
				}
			}
        }

		/// <summary>
		/// Loads the specified plugin.
		/// </summary>
		/// <param name="plugin">The plugin.</param>
		public void Load(PluginBase plugin) {
			//Check if plugin isn't loaded
			if (!plugin.Loaded) {
				try {
					if (plugin.Ready) {
						Object obj = plugin.Assembly.CreateInstance(plugin.Type.FullName);
						if (obj != null) {
							plugin.Plugin = (IPlugin)obj;
							plugin.TotalLoad += 1;
							if (obj is IPluginHost) {
								this.Host(plugin);
							}
						}
					}
				} catch {
					plugin.TotalFail += 1;

				}
			}
		}
		/// <summary>
		/// Loads the default plugins.
		/// </summary>
		public void LoadDefault() {
			_playbackAudio = _plugins.FindOne<IPlayback>();
			_storage = _plugins.FindOne<IStorage>();
			_watcher = _plugins.FindOne<IWatcher>();
			_tagActive = _plugins.FindOne<ITagProvider>();
			_playControl = _plugins.FindOne<IPlayControl>();
			_settings = _plugins.FindOne<ISettings>();
			_plugins.LoadAll();
		}

		/// <summary>
		/// Reads the config from default path.
		/// </summary>
		public void ReadConfig() {
			this.ReadConfig(Directory.GetCurrentDirectory() + "\\Plugin\\Plugin.xml");
		}
		/// <summary>
		/// Reads the config for specified path.
		/// </summary>
		/// <param name="filename">The filename.</param>
		public void ReadConfig(string filename) {
			//Check if file exists
			if (File.Exists(filename)) {
				//Good, let's try to read it
				XmlDocument doc = new XmlDocument();
				//try to read
				try {
					doc.Load(filename);
					//We are still lucky
					if (doc != null) {
						this.ReadXmlDocument(doc);
					}
				} catch {
					//Mysterious stuff might happen
				}
			}
		}
		/// <summary>
		/// Saves the config to default location.
		/// </summary>
		public void SaveConfig() {
			this.SaveConfig("Plugin\\Plugin.xml");
		}
		/// <summary>
		/// Saves the config to specified location
		/// </summary>
		/// <param name="filename">The filename.</param>
		public void SaveConfig(string filename) {
			//Init document and root nodes.
			XmlDocument doc = new XmlDocument();
			XmlDeclaration xmlRoot = doc.CreateXmlDeclaration("1.0", "UTF-8", "no");
			doc.AppendChild(xmlRoot);
			XmlElement rootNode = doc.CreateElement("Plugins");
			//loop through plugins
			PluginBase[] arrayPlugins = Config.Plugins.Plugins.ToArray();
			if (arrayPlugins != null && arrayPlugins.Length > 0) {
				foreach (PluginBase plugin in arrayPlugins) {
					if (plugin != null) {
						//Create plugin element
						XmlElement xmlPlugin = doc.CreateElement("Plugin");
						//Create attributes
						//Base attributes
						XmlAttribute attGuid = doc.CreateAttribute("Guid");
						attGuid.Value = plugin.Guid.ToString();
						xmlPlugin.Attributes.Append(attGuid);

						XmlAttribute attAuto = doc.CreateAttribute("Auto");
						attAuto.Value = plugin.AutoLoad.ToString();
						xmlPlugin.Attributes.Append(attAuto);

						XmlAttribute attLoad = doc.CreateAttribute("Loaded");
						attLoad.Value = plugin.TotalLoad.ToString();
						xmlPlugin.Attributes.Append(attLoad);

						XmlAttribute attFail = doc.CreateAttribute("Failed");
						attFail.Value = plugin.TotalFail.ToString();
						xmlPlugin.Attributes.Append(attFail);
						//Add attributes
						rootNode.AppendChild(xmlPlugin);
					}
				}
			}
			//Add root node and save everyting
			doc.AppendChild(rootNode);
			doc.Save(filename);
		}
		#endregion
		#region Property
		public PluginHash Plugins {
			get { return _plugins; }
			set { _plugins = value; }
		}

		public IPlayback PlaybackAudio {
			get {
				if (_playbackAudio != null) {
					if (!_playbackAudio.Loaded) {
						_playbackAudio.Load();
					}
					return _playbackAudio.Plugin;
				}
				return null;
			}
		}
		public ITagProvider TagProvider {
			get {
				if (_tagActive != null) {
					if (!_tagActive.Loaded) {
						_tagActive.Load();
					}
					return _tagActive.Plugin;
				}
				return null;
			}
		}
		public IWatcher Watcher {
			get {
				if (_watcher != null) {
					if (!_watcher.Loaded) {
						_watcher.Load();
					}
					return _watcher.Plugin;
				}
				return null;
			}
		}
		public IStorage Storage {
			get {
				if (_storage != null) {
					if (!_storage.Loaded) {
						_storage.Load();
					}
					return _storage.Plugin;
				}
				return null;
			}
		}
		public IPlayControl PlayControl {
			get {
				if (_playControl != null) {
					if (!_playControl.Loaded) {
						_playControl.Load();
					}
					return _playControl.Plugin;
				}
				return null;
			}
		}
		public ISettings Settings {
			get {
				if (_settings != null) {
					if (!_settings.Loaded) {
						_settings.Load();
					}
					return _settings.Plugin;
				}
				return null;
			}
		}
		#endregion
		#region Property Bools
		/// <summary>
		/// Gets a value indicating whether this Host has storage.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if this Host has storage; otherwise, <c>false</c>.
		/// </value>
		public bool HasStorage {
			get {
				if (this.Storage != null) {
					return true;
				} else {
					return false;
				}
			}
		}

		/// <summary>
		/// Gets a value indicating whether this host has playback.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if this host has playback; otherwise, <c>false</c>.
		/// </value>
		public bool HasPlayback {
			get {
				if (this.PlaybackAudio != null) {
					return true;
				} else {
					return false;
				}
			}
		}

		/// <summary>
		/// Gets a value indicating whether this host has tag provider.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if this host has tag provider; otherwise, <c>false</c>.
		/// </value>
		public bool HasTagProvider {
			get {
				if (this.TagProvider != null) {
					return true;
				} else {
					return false;
				}
			}
		}

		/// <summary>
		/// Gets a value indicating whether this instance has play controler.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if this instance has play controler; otherwise, <c>false</c>.
		/// </value>
		public bool HasPlayControl {
			get {
				if (this.PlayControl != null) {
					return true;
				} else {
					return false;
				}
			}
		}
		#endregion
		#region Events
		public event Action<PluginBase> PluginFound;
		#endregion
	}
}